package ml;

import java.util.ArrayList;

public class Trading {
	private double m_asset = 0.0;
	private ArrayList<Ticket> m_tickets = null;
	private int m_decision_time = 4;
	private double m_decision_rate = 0.6;
	private ArrayList<Double> m_decisions = null;
	private double m_init_stop_loss = 0.002;
	private double m_init_take_profit = 0.008;
	private double m_leverage = 1.0;
	private ArrayList<Double> m_assets = new ArrayList<Double>();
	private ArrayList<Double> m_buy_vols = new ArrayList<Double>();
	private ArrayList<Double> m_sell_vols = new ArrayList<Double>();

	private boolean m_strategy_good = true;
	private ArrayList<Double> m_trade_profits = null;

	private double m_max_vols = 0.0;
	private double m_last_price = 0.0;

	public Trading(double asset, int decision_time, double decision_rate, double stop_loss,
					double take_profit, double leverage) {
		m_asset = asset;
		m_tickets = new ArrayList<Ticket>();
		m_decision_time = decision_time;
		m_decision_rate = decision_rate;
		m_decisions = new ArrayList<Double>(m_decision_time);
		for (int i = 0; i < m_decision_time; ++i) {
			m_decisions.add(0.0);
		}
		m_init_stop_loss = stop_loss;
		m_init_take_profit = take_profit;
		m_leverage = leverage;
		m_assets.add(m_asset);
		m_buy_vols.add(0.0);
		m_sell_vols.add(0.0);
		m_trade_profits = new ArrayList<Double>(MLConstants.JUDGE_STRATEGY_NUM);
		for (int i = 0; i < MLConstants.JUDGE_STRATEGY_NUM; ++i) {
			m_trade_profits.add(0.0);
		}
	}

	public ArrayList<Double> get_assets() {
		return m_assets;
	}
	
	public ArrayList<Double> get_buy_vols() {
		return m_buy_vols;
	}

	public ArrayList<Double> get_sell_vols() {
		return m_sell_vols;
	}
	
	public void Update(ArrayList<Double> prices, ArrayList<Boolean> rise_labels,
					ArrayList<Boolean> drop_labels, boolean close_at_end) {
		assert (prices.size() == rise_labels.size());
		assert (prices.size() == drop_labels.size());

		if (prices.size() > 0) {
			m_last_price = prices.get(0);
			for (int i = 0; i < prices.size(); ++i) {
				Update(prices.get(i), rise_labels.get(i), drop_labels.get(i));
			}
			if (close_at_end) {
				UpdateAtEnd(prices.get(prices.size() - 1));
			}
		}

		System.out.println("Max Vols: " + m_max_vols);
		System.out.println();
	}

	private void Update(double price, boolean rise_label, boolean drop_label) {
		if (m_asset < Double.MIN_VALUE) {
			return;
		}

		double vols = 0.0;
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				vols += aTicket.m_volume;
				UpdateAsset(price, aTicket);
				CheckClose(price, aTicket, false);
				CheckMoveStopLoss(price, aTicket);
			}
		}
		m_last_price = price;
		
		if (vols > m_max_vols) {
			m_max_vols = vols;
		}

		UpdateDecisions(rise_label, drop_label);

		ArrayList<Ticket> new_tickets = new ArrayList<Ticket>();
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				new_tickets.add(aTicket);
			}
		}
		m_tickets = new_tickets;

		m_assets.add(m_asset);

		CheckBuyOrSell(price);
		
		UpdateVolumes();
	}

	private void UpdateAtEnd(double price) {
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				CheckClose(price, aTicket, true);
			}
		}
	}

	private void UpdateTradeProfits(double profit) {
		while (m_trade_profits.size() >= MLConstants.JUDGE_STRATEGY_NUM) {
			m_trade_profits.remove(0);
		}
		m_trade_profits.add(profit);
	}

	private void UpdateAsset(double price, Ticket ticket) {
		if (m_strategy_good) {
			double profit = 0.0;
			if (ticket.m_is_buy) {
				profit = (price - m_last_price);
			} else {
				profit = (m_last_price - price);
			}
			m_asset += profit * ticket.m_volume;
		}
	}

	private void UpdateVolumes() {
		double buy_vols = 0.0;
		double sell_vols = 0.0;
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				if (aTicket.m_is_buy) {
					buy_vols += aTicket.m_volume;
				} else {
					sell_vols += aTicket.m_volume;
				}
			}
		}
		m_buy_vols.add(buy_vols);
		m_sell_vols.add(sell_vols);
	}

	private void UpdateDecisions(boolean rise_label, boolean drop_label) {
		if (rise_label && (!drop_label)) {
			m_decisions.add(1.0);
		} else if (drop_label && (!rise_label)) {
			m_decisions.add(-1.0);
		}

		while (m_decisions.size() > m_decision_time) {
			m_decisions.remove(0);
		}
	}

	private void CheckClose(double price, Ticket ticket, boolean close_at_end) {
		if (ticket.m_is_buy) {
			if ((price >= ticket.m_take_profit) || (price <= ticket.m_stop_loss) || close_at_end) {
				CloseTicket(price, ticket);
			}
		} else {
			if ((price <= ticket.m_take_profit) || (price >= ticket.m_stop_loss) || close_at_end) {
				CloseTicket(price, ticket);
			}
		}
	}

	private void JudgeStrategy() {
		if (m_trade_profits.size() >= MLConstants.JUDGE_STRATEGY_NUM) {
			double all_profit = 0.0;
			for (double profit : m_trade_profits) {
				all_profit += profit;
			}
			if (all_profit < (MLConstants.JUDGE_STRATEGY_NUM * MLConstants.BAD_STRATEGY_THRESHOLD)) {
				m_strategy_good = false;
			} else if (all_profit > (MLConstants.JUDGE_STRATEGY_NUM * MLConstants.GOOD_STRATEGY_THRESHOLD)) {
				m_strategy_good = true;
			}
		}
	}

	private void CloseTicket(double price, Ticket ticket) {
		assert (!ticket.m_is_closed);
		double profit = 0.0;
		if (ticket.m_is_buy) {
			profit = (price - ticket.m_open_price);
		} else {
			profit = (ticket.m_open_price - price);
		}
		UpdateTradeProfits(profit);
		JudgeStrategy();

		ticket.m_is_closed = true;
	}

	private void CheckMoveStopLoss(double price, Ticket ticket) {
		if (ticket.m_is_closed) {
			return;
		}
		// TODO: change the fixed moving stop loss to variable stop loss. Need
		// to analyze this algorithm.
		double a = 4 * m_init_stop_loss * m_init_take_profit * m_init_take_profit;
		double b = ticket.m_open_price + 0.5 * m_init_take_profit;
		double c = m_init_take_profit / 6.0;
		double stop_loss = a * Math.pow(price - b, 2) + c;
		if (stop_loss < m_init_stop_loss)
			stop_loss = m_init_stop_loss;

		if (ticket.m_is_buy) {
			if (price >= ticket.m_stop_loss) {
				ticket.m_stop_loss = price - stop_loss;
			}
		} else {
			if (price <= ticket.m_stop_loss) {
				ticket.m_stop_loss = price + stop_loss;
			}
		}
	}

	/*
	 * Return minus if should not trade.
	 */
	private double CalVols(double price, boolean is_buy) {
		double has_vols = 0.0;
		for (Ticket aTicket : m_tickets) {
			if (!aTicket.m_is_closed) {
				if (aTicket.m_is_buy == is_buy) {
					has_vols += aTicket.m_volume;
				}
			}
		}
		double max_vols = m_asset * MLConstants.MAX_LEVERAGE / (price + MLConstants.SPREAD);
		
		double vols = MLConstants.FIXED_VOLS;
		if (vols < 0.0) {
			vols = m_asset * m_leverage / (price + MLConstants.SPREAD);
		}
		if (vols > MLConstants.MAX_VOLS) {
			vols = MLConstants.MAX_VOLS;
		}
		vols = Math.min(max_vols-has_vols, vols);
		return vols;
	}

	private void CheckBuyOrSell(double price) {
		if (m_decisions.size() <= 0) {
			return;
		} else if (m_decisions.get(m_decisions.size() - 1) > 0.5) {
			int num_same_order = 0;
			for (Ticket aTicket : m_tickets) {
				if (!aTicket.m_is_closed) {
					if (aTicket.m_is_buy) {
						if (aTicket.Profit(price) < 0) {
							num_same_order += MLConstants.MAX_ORDERS_NUM;
						}
					} else {
						CloseTicket(price, aTicket);
					}
				}
			}
			if (num_same_order >= MLConstants.MAX_ORDERS_NUM) {
				return;
			}
			int votes = 0;
			int votes_threshold = (int) Math.ceil(m_decision_rate * m_decision_time);
			for (double label : m_decisions) {
				if (label > 0.5) {
					++votes;
					if (votes >= votes_threshold) {
						double vols = CalVols(price, true);
						if (vols < 0) {
							return;
						}
						Ticket newTicket = new Ticket(true, vols, price + MLConstants.SPREAD, price
										+ MLConstants.SPREAD - m_init_stop_loss, price
										+ MLConstants.SPREAD + m_init_take_profit);
						m_tickets.add(newTicket);
						m_decisions.clear();
						return;
					}
				}
			}
		} else if (m_decisions.get(m_decisions.size() - 1) < -0.5) {
			int num_same_order = 0;
			for (Ticket aTicket : m_tickets) {
				if (!aTicket.m_is_closed) {
					if (!aTicket.m_is_buy) {
						if (aTicket.Profit(price) < 0) {
							num_same_order += MLConstants.MAX_ORDERS_NUM;
						}
					} else {
						CloseTicket(price, aTicket);
					}
				}
			}
			if (num_same_order >= MLConstants.MAX_ORDERS_NUM) {
				return;
			}
			int votes = 0;
			int votes_threshold = (int) Math.ceil(m_decision_rate * m_decision_time);
			for (double label : m_decisions) {
				if (label < -0.5) {
					++votes;
					if (votes >= votes_threshold) {
						double vols = CalVols(price, false);
						if (vols < 0) {
							return;
						}
						Ticket newTicket = new Ticket(false, vols, price - MLConstants.SPREAD,
										price - MLConstants.SPREAD + m_init_stop_loss, price
														- MLConstants.SPREAD - m_init_take_profit);
						m_tickets.add(newTicket);
						m_decisions.clear();
						return;
					}
				}
			}
		}
	}
}
